﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Scripting;
using Microsoft.Scripting.Ast;
using Microsoft.Linq.Expressions;

namespace DlrBasic.Parser.Ast
{
    class For : Statement
    {
        VariableOrCall variable;
        DlrBasicExpression initial;
        DlrBasicExpression bound;
        DlrBasicExpression step;

        public For(SourceSpan span, VariableOrCall variable, DlrBasicExpression initial, DlrBasicExpression bound)
            : this(span, variable, initial, bound, null)
        {
        }

        public For(SourceSpan span, VariableOrCall variable, DlrBasicExpression initial, DlrBasicExpression bound, DlrBasicExpression step)
            : base(span)
        {
            this.variable = variable;
            this.initial = initial;
            this.bound = bound;
            this.step = step;
        }

        private string GetForId()
        {
            return variable.GetFullVariableName();
        }

        protected internal override Expression Generate(DlrBasicGenerator generator, StateMemberGenerator stateParameter, LambdaBuilder builder)
        {
            List<Expression> forExpressions = new List<Expression>();
            List<Expression> startForExpressions = new List<Expression>();

            Expression initalAssigment =
                variable.GenerateAssign(generator, initial.Generate(generator));
            forExpressions.Add(initalAssigment);
            
            Expression<Runtime.NextStateDelegate> continueAction =
                CreateContinueAction(generator);

            Expression registerFor = Expression.Call(stateParameter.GetForStackVariable(),
                typeof(Runtime.DlrBasicForStack).GetMethod("AddFor"),
                Expression.Constant(GetForId()),
                Expression.Constant(generator.GetNextStatementNumber(this)),
                continueAction);

            startForExpressions.Add(registerFor);
            startForExpressions.Add(base.Generate(generator, stateParameter, builder));

            Next correspondingNextStatement = (Next)GetCorrespondingNext(generator);
            if (correspondingNextStatement != null)
            {
                forExpressions.Add(
                    CreateInitailCheck(generator, stateParameter,
                    correspondingNextStatement, startForExpressions));
            }
            else
                forExpressions.AddRange(startForExpressions);

            return Expression.Block(forExpressions.ToArray());
        }

        private Expression CreateInitailCheck(DlrBasicGenerator generator, StateMemberGenerator stateParameter,
            Next correspondingNextStatement, List<Expression> startForExpressions)
        {
//            return Expression.Block(startForExpressions.ToArray());

            if (correspondingNextStatement.HasForIds() && correspondingNextStatement.GetForIds().Length > 1)
                throw new NotSupportedException();

            Expression abortForExpression = Expression.Assign(stateParameter.GetCurrentStatementVariable(),
                Expression.Constant(generator.GetNextStatementNumber(correspondingNextStatement)));

            Expression stepExpression = CreateStepExpression(generator);
            Expression boundExpression = bound.Generate(generator);
            Expression variableExpression = variable.Generate(generator);
            
            return Utils.IfThenElse(
                CreateBoundCheck(stepExpression, boundExpression, variableExpression),
                Expression.Block(startForExpressions.ToArray()),
                abortForExpression);
        }

        private Statement GetCorrespondingNext(DlrBasicGenerator generator)
        {
            string forId = this.GetForId();
            Statement nextStatement = this;
            Stack<string> forIds = new Stack<string>();
            do
            {
                nextStatement = generator.GetNextStatement(nextStatement);
                if(nextStatement is For)
                {
                    forIds.Push(((For)nextStatement).GetForId());
                }
                else if (nextStatement is Next)
                {
                    if (!((Next)nextStatement).HasForIds())
                    {
                        if (forIds.Count == 0)
                            return nextStatement;
                        else
                            forIds.Pop();
                    }
                    else
                    {
                        string[] ids = ((Next)nextStatement).GetForIds();
                        for (int i = 0; i < ids.Length; i++)
                        {
                            if (ids[i] == forId) return nextStatement;

                            while (forIds.Count > 0 && forIds.Peek() != ids[0])
                                forIds.Pop();

                            if (forIds.Count == 0) return null;
                            forIds.Pop();
                        }
                    }
                }
            } while(nextStatement != null);
            return null;
        }

        private Expression CreateStepExpression(DlrBasicGenerator generator)
        {
            Expression stepExpression;
            if (step == null)
                stepExpression = Expression.Constant((short)1);
            else
                stepExpression = step.Generate(generator);
            return stepExpression;
        }

        private Expression<Runtime.NextStateDelegate> CreateContinueAction(DlrBasicGenerator generator)
        {
            LambdaBuilder builder = Utils.Lambda(typeof(bool), "<for>");
            
            Expression stepExpression = CreateStepExpression(generator);
            Expression boundExpression = bound.Generate(generator);
            Expression variableExpression = variable.Generate(generator);

            Expression comparison =
                CreateBoundCheck(stepExpression, boundExpression, variableExpression);

            System.Reflection.MethodInfo method;
            Expression converted1, converted2;
            TypeConversionUtils.FindMethodAndConvertOperands(typeof(Runtime.DlrBasicMath),
                "Add", variableExpression, stepExpression, 
                out method, out converted1, out converted2);
            Expression variableChange = variable.GenerateAssign(generator,
                Expression.Call(method, converted1, converted2));

            Expression body = Expression.Comma(variableChange, comparison);

            return Expression.Lambda<Runtime.NextStateDelegate>(body);
        }

        private static Expression CreateBoundCheck(Expression stepExpression, Expression boundExpression, Expression variableExpression)
        {
            System.Reflection.MethodInfo method;
            Expression converted1, converted2;

            TypeConversionUtils.FindMethodAndConvertOperands(typeof(Runtime.DlrBasicCompare),
                "Compare", variableExpression, boundExpression,
                out method, out converted1, out converted2);

            Expression positiveComparison = Expression.Call(
                method, Expression.Constant(Runtime.RelationalOperator.LessThanOrEqualTo),
                converted1, converted2);

            Expression negativeComparison = Expression.Call(
                method, Expression.Constant(Runtime.RelationalOperator.GreaterThanOrEqualTo),
                converted1, converted2);

            TypeConversionUtils.FindMethodAndConvertOperands(typeof(Runtime.DlrBasicCompare),
                "Compare", stepExpression, Expression.Constant((short)0),
                out method, out converted1, out converted2);
            Expression testExpression = TypeConversionUtils.BuildTestIfTrue(Expression.Call(
                method, Expression.Constant(Runtime.RelationalOperator.GreaterThan),
                converted1, converted2));

            return TypeConversionUtils.BuildTestIfTrue(
                Expression.Condition(testExpression, positiveComparison, negativeComparison));
        }
    }
    
}
